Tutorial 1: Automated Refinement#

Dara provides a basic wrapper to the refinement software BGMN. It implements a robust optimization algorithm that can refine automatically in most cases. This tutorial will show you how to interact with BGMN software and how to submit, adjust, and visualize your refinements.

from dara.refine import do_refinement_no_saving
from pathlib import Path
data = Path("tutorial_data")
cif_paths = list(data.glob("*.cif"))  # include all the cif files in the data folder

pattern_fn = "CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.xy"

Basic Refinement#

A one-line refinement with the default settings.

Do refinement#

By running do_refinement_no_saving, the refinement will be performed and the results will be printed out. There will be no BGMN refinement saved in the disk.

The only two things you will need to feed into the system:

  • the path to the pattern. Currently, Dara only supports the xy, xrdml, raw formats.

  • a list of cif file paths. The cif will be used as the reference structure for the refinement.

refinement = do_refinement_no_saving(data / pattern_fn, cif_paths)
2024-06-26 07:54:48,115 WARNING dara.bgmn_worker BGMN executable not found. Downloading BGMN.
  0%|          | 0.00/1.59M [00:00<?, ?iB/s]
  3%|▎         | 48.1k/1.59M [00:00<00:03, 470kiB/s]
 14%|█▎        | 216k/1.59M [00:00<00:01, 1.16MiB/s]
 50%|█████     | 801k/1.59M [00:00<00:00, 3.26MiB/s]
 92%|█████████▏| 1.47M/1.59M [00:00<00:00, 4.59MiB/s]
100%|██████████| 1.59M/1.59M [00:00<00:00, 3.87MiB/s]

Visualization#

You can call visualize to visualize the refinement results. The observed, calculated, and difference patterns will be plotted.

refinement.visualize()

Save the refinement plot#

Optionally, if you want to share the plot with others, you can save the plot by calling write_iamge or write_html in the plotly.Figure object returned by .visualize(). The plot will be saved in the disk.

refinement.visualize().write_html("tutorial_refinement.html")  # output the interactive html file to the disk
refinement.visualize().write_image("tutorial_refinement.png")  # output the png image to the disk

Refining with customized phase parameters#

The refinement with default setting looks good. But can it be better?

Dara supports the basic refinement parameters in BGMN. You can adjust the refinement parameters by passing the parameters to the do_refinement_no_saving function.

Common parameters include:

  • lattice_range: you can (and need to) specify the range that the lattice parameters can vary. Usually, it can be a small range, like 0.01 ~ 0.05. It is applied to all lattice parameters (a, b, c, alpha, beta, gamma).

  • b1: controls the width of the peak. b1 describes the average particle size in the XRD sample. The larger the b1, the broader the peak. Usually, it is constraint to a small range, like from 0 to 0.005. If the b1 is too large, you will see the peaks go too broad. In this case, your simulated pattern will look like an amorphous material that can be easily fit into the background.

  • k1: controls the width of the peak. k1 describes how width the particle size distribute in the sample. The larger the k1, the smaller the distribution is. Usually, it can be constraint to 0 ~ 1.

  • k2: describes the microstrain in the sample. The larger the k2, the larger the microstrain. Usually, it can be a fixed value, like 0.

  • gewicht: it contains the information of scale factor. However, in BGMN, it can also be used to specify the preferred orientation you would like to use in the refinement. By specifying the preferred orientation, you can vary the intensity of a set of reflections in the pattern, which can help you fit your pattern better. BGMN is able to decide which reflection to adjust automatically. You only need to specify how strong the preferred orientation is. Usually, it can be SPHAR0 (none), SPHAR2 (two preferred orientation parameters), or SPHAR4 (four preferred orientation parameters), … (up to SPHAR10). The larger the gewicht, the stronger the preferred orientation is. But it can cause overfitting as well.

Input parameter format#

In Dara, all the phase parameters are passed to phase_params as a dictionary. The key is the parameter name, and the value is the parameter value. Dara supports three types of values:

  • fixed. This is a string. The paramter will be fixed to the default value (usually 0).

  • (initial value)_(min value)^(max value). This is a string. The parameter will be varied from the initial value to the min value to the max value. The min value begins with _, and the max value begins with ^.

  • Other values. It can be a string or a number. For example, setting gewicht to SPHAR2 means that the preferred orientation is SPHAR2; setting lattice_range to 0.05 means that the lattice parameters can vary up to 5%.

If you want to allow 5% variation in lattice parameters, b1 to be (started from 0, min = 0, max = 0.005), k1 to be (started from 0, min = 0, max = 1), k2 fixed to 0, and gewicht to SPHAR2, you can pass the following dictionary to phase_params:

phase_params = {
    'lattice_range': 0.05,
    'b1': "0_0^0.005",
    'k1': "0_0^1",
    'k2': "fixed",
    'gewicht': 'SPHAR2'
}
refinement = do_refinement_no_saving(data / pattern_fn, cif_paths, phase_params={
    'lattice_range': 0.05,
    'b1': "0_0^0.005",
    'k1': "0_0^1",
    'k2': "fixed",
    'gewicht': 'SPHAR2'
})
refinement.visualize()

Now you can see the result is slightly better.

Specify different parameters for different phases#

In the previous example, the refinement option is applied to all phases. But you can also specify different parameters for different phases. To do so, you will need to pass a special RefinementPhase object to the phases parameter.

from dara import RefinementPhase

phases = [RefinementPhase.make(cif_path) for cif_path in cif_paths]

for phase in phases:
    # use a smaller lattice range for each phase
    phase.params['lattice_range'] = 0.01


refinement = do_refinement_no_saving(
    data / pattern_fn, 
    phases=phases,
    # if one parameter is both specified in phase_params and in the RefinementPhase object, the value in RefinementPhase will be used.
    phase_params={
        'lattice_range': 0.05,  # <- this will be ignored because it has already been set in the eahc RefinementPhase object
        'b1': "0_0^0.005",
        'k1': "0_0^1",
        'k2': "fixed",
        'gewicht': 'SPHAR2'
    }
)

Refining with different instrument profiles, angle range, wavelength.#

If you would like to do refinement in a different instrument profiles or angle range, you can specify it in the refinement function as well.

  • instrument_name: the instrument profile you would like to use. You can find the available instrument profiles in the dara/data/BGMN-Templates/Devices folder.

  • wavelength: the wavelength you would like to use in the refinement. It can be two types:

    • a number: the wavelength in nm. It is useful when you analyzing the data from a synchrotron.

    • a string: the element symbol. It represents the target material in the X-ray tube. BGMN can automatically find the distribution of the wavelength for the given metal. Currently, it supports sources of [“Cu”, “Co”, “Cr”, “Fe”, “Mo”]

  • wmin, wmax: the angle range you would like to use in the refinement. It is set in refinement_params.

refinement = do_refinement_no_saving(
    data / pattern_fn,
    cif_paths, 
    instrument_name="Aeris-fds-Pixcel1d-Medipix3",
    wavelength="Cu",
    refinement_params={
        "wmin": 20,  # set the minimum two-thera in the refinement to be 20 deg.
        "wmax": 50  # set the maximum two-theta in the refinement to be 50 deg.
    }
)
refinement.visualize()

Save the proejct file to a folder#

Usually you don’t have to read the refinement file. But if you would like to save the refinement file, you can call the do_refinement function. The refinement file will be saved in the path specified by working_dir. Other than that, do_refinement and do_refinement_no_saving share the same parameters and output.

You can modify the refinement project file yourself or with help from Profex software.

from dara import do_refinement

refinement = do_refinement(data / pattern_fn, cif_paths, working_dir="tutorial_refinement")
refinement_folder = Path("tutorial_refinement")

# show all the files in the folder
for file in refinement_folder.glob("*"):
    print(">", file.name)
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.par
> Aeris-fds-Pixcel1d-Medipix3.sav
> CaNi(PO3)4_15_sym.str
> Aeris-fds-Pixcel1d-Medipix3.geq
> Aeris-fds-Pixcel1d-Medipix3.tpl
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.sav
> Aeris-fds-Pixcel1d-Medipix3.ger
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.xy
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.lst
> NiO_225_sym.str
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.dia